#include "Interpreter.h"

#include <fstream>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <limits>

std::unordered_map<char, std::string> code_color = {
		{ '+', "\033[33m" },
		{ '-', "\033[33m" },
		{ '<', "\033[34m" },
		{ '>', "\033[34m" },
		{ '[', "\033[36m" },
		{ ']', "\033[36m" },
		{ ',', "\033[32m" },
		{ '.', "\033[32m" },
};

nmspc::Interpreter::Interpreter() :
	max_capacity(65536)
{
	this->paper_tape = std::move(std::vector<char>(max_capacity, 0));
}

std::vector<std::string> nmspc::Interpreter::split(const std::string& cmd, const char& skip)
{
	std::vector<std::string> ans;
	std::string assist;
	for (auto& ch : cmd)
	{
		if (ch == skip && !assist.empty())
		{
			ans.push_back(assist);
			assist.clear();
		}
		else
		{
			assist.push_back(ch);
		}
	}
	if (!assist.empty())ans.push_back(assist);
	return ans;
}

std::int32_t nmspc::Interpreter::stepExecute(const std::string& full_code, std::int64_t& code_index, bool& if_could_execute, std::int64_t& index, std::stack<std::int64_t>& left_square_bracket_index)
{
	char ch = full_code[code_index];
	if (if_could_execute)
	{
		if (ch == '+')
		{
			this->paper_tape[index]++;
			code_index++;
		}
		else if (ch == '-')
		{
			this->paper_tape[index]--;
			code_index++;
		}
		else if (ch == '<')
		{
			index--;
			index += this->paper_tape.size();
			index %= this->paper_tape.size();
			code_index++;
		}
		else if (ch == '>')
		{
			index++;
			index %= this->paper_tape.size();
			code_index++;
		}
		else if (ch == ',')
		{
			while (1)
			{
				std::cerr << "\033[35m" << "Input: " << "\033[0m" << std::flush;
				if (std::cin.fail() || std::cin.bad())
				{
					std::cin.clear();
				}
				std::string cur_input;
				std::cin >> cur_input;
				if (!cur_input.empty())
				{
					if (cur_input.size() == 1)
					{
						this->paper_tape[index] = cur_input[0];
						break;
					}
					else if (cur_input[0] == 'n' || cur_input[0] == 'N')
					{
						cur_input.erase(cur_input.begin());
						try
						{
							this->paper_tape[index] = std::stoi(cur_input);
							break;
						}
						catch (const std::exception& e)
						{
							std::cerr << "\033[31m" << "Error input! Please try again!" << "\033[0m" << std::endl;
							std::cerr << "\033[31m" << "Error: " << "\033[0m" << e.what() << std::endl;
						}
					}
					else
					{
						std::cerr << "\033[31m" << "Error input! Please try again!" << "\033[0m" << std::endl;
					}
				}
			}
			std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
			code_index++;
		}
		else if (ch == '.')
		{
			std::cerr << "\033[35m" << "Output: " << "\033[0m" << this->paper_tape[index] << std::endl;
			code_index++;
		}
		else
		{
			for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
			{
				if (code_color.find(full_code[code_index]) != code_color.end())
				{
					break;
				}
			}
		}
	}
	else
	{
		for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
		{
			if (full_code[code_index] == '[')
			{
				left_square_bracket_index.push(code_index);
			}
			else if (full_code[code_index] == ']')
			{
				if (!left_square_bracket_index.empty())
				{
					left_square_bracket_index.pop();
					if (left_square_bracket_index.empty())
					{
						if_could_execute = true;
						code_index++;
						ch = 0;
						break;
					}
				}
				else
				{
					std::cerr << "\033[31m" << "Error Code! The loop body is not closed!" << "\033[0m" << std::endl;
					return 1;
				}
			}
		}
	}
	if (ch == '[')
	{
		if_could_execute = (this->paper_tape[index] != 0);
		if (if_could_execute)
		{
			left_square_bracket_index.push(code_index);
			code_index++;
		}
		else
		{
			for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
			{
				if (full_code[code_index] == '[')
				{
					left_square_bracket_index.push(code_index);
				}
				else if (full_code[code_index] == ']')
				{
					if (!left_square_bracket_index.empty())
					{
						left_square_bracket_index.pop();
						if (left_square_bracket_index.empty())
						{
							if_could_execute = true;
							code_index++;
							ch = 0;
							break;
						}
					}
					else
					{
						std::cerr << "\033[31m" << "Error Code! The loop body is not closed!" << "\033[0m" << std::endl;
						return 1;
					}
				}
			}
		}
	}
	else if (ch == ']')
	{
		if (left_square_bracket_index.empty())
		{
			std::cerr << "\033[31m" << "Error Code! The loop body is not closed!" << "\033[0m" << std::endl;
			return 1;
		}
		if_could_execute = true;
		if (this->paper_tape[index] == 0)
		{
			code_index++;
			ch = 0;
			left_square_bracket_index.pop();
		}
		else
		{
			code_index = left_square_bracket_index.top() + 1;
		}
	}
	else
	{
		for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
		{
			if (code_color.find(full_code[code_index]) != code_color.end())
			{
				break;
			}
		}
	}
	return 0;
}

std::int32_t nmspc::Interpreter::fastExecute(const std::string& full_code, std::int64_t& code_index, bool& if_could_execute, std::int64_t& index, std::stack<std::int64_t>& left_square_bracket_index, bool if_visible)
{
	std::cerr << std::endl;
	std::cerr << std::endl;
	std::cerr << std::endl;
	std::uint64_t full_code_size = full_code.size();
	std::string final_output;
	while (code_index < full_code_size)
	{
		char ch = full_code[code_index];
		if (if_could_execute)
		{
			if (ch == '+')
			{
				this->paper_tape[index]++;
				code_index++;
			}
			else if (ch == '-')
			{
				this->paper_tape[index]--;
				code_index++;
			}
			else if (ch == '<')
			{
				index--;
				index += this->paper_tape.size();
				index %= this->paper_tape.size();
				code_index++;
			}
			else if (ch == '>')
			{
				index++;
				index %= this->paper_tape.size();
				code_index++;
			}
			else if (ch == ',')
			{
				std::int32_t log_count = 0;
				while (1)
				{
					std::cerr << "\033[35m" << "Input: " << "\033[0m" << std::flush;
					if (std::cin.fail() || std::cin.bad())
					{
						std::cin.clear();
					}
					std::string cur_input;
					std::cin >> cur_input;
					log_count++;
					if (!cur_input.empty())
					{
						if (cur_input.size() == 1)
						{
							this->paper_tape[index] = cur_input[0];
							break;
						}
						else if (cur_input[0] == 'n' || cur_input[0] == 'N')
						{
							cur_input.erase(cur_input.begin());
							try
							{
								this->paper_tape[index] = std::stoi(cur_input);
								break;
							}
							catch (const std::exception& e)
							{
								std::cerr << "\033[31m" << "Error input! Please try again!" << "\033[0m" << std::endl;
								std::cerr << "\033[31m" << "Error: " << "\033[0m" << e.what() << std::endl;
								log_count += 2;
							}
						}
						else
						{
							std::cerr << "\033[31m" << "Error input! Please try again!" << "\033[0m" << std::endl;
							log_count++;
						}
					}
				}
				std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
				for (std::int32_t i = 0; i < log_count; i++)
				{
					std::cerr << "\033[1A";
					std::cerr << "\033[2K";
				}
				code_index++;
			}
			else if (ch == '.')
			{
				char outputable_ch = (
					(this->paper_tape[index] >= 0 && this->paper_tape[index] <= 25) ||
					(this->paper_tape[index] >= 27 && this->paper_tape[index] <= 31) ||
					(this->paper_tape[index] == 127) ||
					(this->paper_tape[index] >= -127 && this->paper_tape[index] <= -2)
					) ? ' ' : this->paper_tape[index];
				final_output += outputable_ch;
				code_index++;
			}
			else
			{
				for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
				{
					if (code_color.find(full_code[code_index]) != code_color.end())
					{
						break;
					}
				}
			}
		}
		else
		{
			for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
			{
				if (full_code[code_index] == '[')
				{
					left_square_bracket_index.push(code_index);
				}
				else if (full_code[code_index] == ']')
				{
					if (!left_square_bracket_index.empty())
					{
						left_square_bracket_index.pop();
						if (left_square_bracket_index.empty())
						{
							if_could_execute = true;
							code_index++;
							ch = 0;
							break;
						}
					}
					else
					{
						std::cerr << "\033[31m" << "Error Code! The loop body is not closed!" << "\033[0m" << std::endl;
						return 1;
					}
				}
			}
		}
		if (ch == '[')
		{
			if_could_execute = (this->paper_tape[index] != 0);
			if (if_could_execute)
			{
				left_square_bracket_index.push(code_index);
				code_index++;
			}
			else
			{
				for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
				{
					if (full_code[code_index] == '[')
					{
						left_square_bracket_index.push(code_index);
					}
					else if (full_code[code_index] == ']')
					{
						if (!left_square_bracket_index.empty())
						{
							left_square_bracket_index.pop();
							if (left_square_bracket_index.empty())
							{
								if_could_execute = true;
								code_index++;
								ch = 0;
								break;
							}
						}
						else
						{
							std::cerr << "\033[31m" << "Error Code! The loop body is not closed!" << "\033[0m" << std::endl;
							return 1;
						}
					}
				}
			}
		}
		else if (ch == ']')
		{
			if (left_square_bracket_index.empty())
			{
				std::cerr << "\033[31m" << "Error Code! The loop body is not closed!" << "\033[0m" << std::endl;
				return 1;
			}
			if_could_execute = true;
			if (this->paper_tape[index] == 0)
			{
				code_index++;
				ch = 0;
				left_square_bracket_index.pop();
			}
			else
			{
				code_index = left_square_bracket_index.top() + 1;
			}
		}
		else
		{
			for (; static_cast<std::uint64_t>(code_index) < full_code.size(); code_index++)
			{
				if (code_color.find(full_code[code_index]) != code_color.end())
				{
					break;
				}
			}
		}
		if (if_visible)
		{
			this->clearPrintedPaperTape();
			this->printPaperTape(index);
		}
	}
	if (!final_output.empty())
	{
		std::cerr << "\033[35m" << "Output: " << "\033[0m" << std::endl;
		std::cout << final_output << std::endl;
	}
	return 0;
}

std::int32_t nmspc::Interpreter::interactive(std::unordered_map<std::string, std::vector<std::string>>& cmd_value_map)
{
	std::uint64_t cur_max_capacity = this->max_capacity;
	if (cmd_value_map.find("-c") != cmd_value_map.end() || cmd_value_map.find("--capacity") != cmd_value_map.end())
	{
		std::string option = (cmd_value_map.find("-c") != cmd_value_map.end() ? "-c" : "--capacity");
		if (cmd_value_map[option].size() != 1)
		{
			std::cerr << "\033[31m" << "The option \"" << option << "\" only accepts one value" << "\033[0m" << std::endl;
			return 1;
		}
		try
		{
			cur_max_capacity = std::stoull(cmd_value_map[option][0]);
			this->paper_tape.resize(cur_max_capacity);
			this->max_capacity = cur_max_capacity;
		}
		catch (const std::exception& e)
		{
			std::cerr << "\033[31m" << "Error: " << e.what() << "\033[0m" << std::endl;
			return 1;
		}
	}
	std::int32_t execute_mode = 0;
	if (cmd_value_map.find("-e") != cmd_value_map.end() || cmd_value_map.find("--execute-mode") != cmd_value_map.end())
	{
		std::string option = (cmd_value_map.find("-e") != cmd_value_map.end() ? "-e" : "--execute-mode");
		if (cmd_value_map[option].size() != 1)
		{
			std::cerr << "\033[31m" << "The option \"" << option << "\" only accepts one value" << "\033[0m" << std::endl;
			return 1;
		}
		std::string execute_mode_str = cmd_value_map[option][0];
		if (execute_mode_str == "step")
		{
			execute_mode = 0;
		}
		else if (execute_mode_str == "fast")
		{
			execute_mode = 1;
		}
		else
		{
			std::cerr << "\033[31m" << "No such execut mode: \"" + execute_mode_str + "\"!" << "\033[0m" << std::endl;
			return 1;
		}
	}
	std::int64_t index = 0;
	bool if_visible = (cmd_value_map.find("-v") != cmd_value_map.end() || cmd_value_map.find("--visible") != cmd_value_map.end());
	bool if_break = false;
	bool if_executing = false;
	std::vector<std::string> codes;
	std::string full_code;
	std::stack<std::int64_t> left_square_bracket_index;
	std::int64_t code_index = 0;
	bool if_could_execute = true;
	while (!if_break)
	{
		if (if_executing)
		{
			if (static_cast<std::uint64_t>(code_index) < full_code.size())
			{
				std::int32_t cur_status = 0;
				switch (execute_mode)
				{
				case 0:
					cur_status = this->stepExecute(full_code, code_index, if_could_execute, index, left_square_bracket_index);
					if (cur_status != 0) return cur_status;
					if (if_visible)
					{
						this->printPaperTape(index);
					}
					break;
				case 1:
					cur_status = this->fastExecute(full_code, code_index, if_could_execute, index, left_square_bracket_index, if_visible);
					if (cur_status != 0) return cur_status;
					break;
				}
			}
			else
			{
				if_executing = false;
			}
		}
		else
		{
			std::cerr << "\033[35m" << ">>>" << "\033[0m" << std::flush;
			std::cerr << "\033[?25h";
			std::string cur_code;
			if (std::getline(std::cin, cur_code))
			{
				if (cur_code.empty()) continue;
				if (cur_code[0] == '&')
				{
					cur_code.erase(cur_code.begin());
					std::unordered_map<std::string, std::vector<std::string>> cmd_value_map = std::move(this->parseCommand(this->split(cur_code, ' ')));
					if (cmd_value_map["--commandName"].size() == 1)
					{
						if (cmd_value_map["--commandName"][0] == "show")
						{
							for (const auto& code : codes)
							{
								for (const auto& ch : code)
								{
									if (code_color.find(ch) != code_color.end()) std::cerr << code_color[ch] << ch;
									else std::cerr << "\033[0m" << ch;
								}
								std::cerr << std::endl;
							}
						}
						else if (cmd_value_map["--commandName"][0] == "exit")
						{
							std::cerr << "\033[32m" << "Bye!" << "\033[0m" << std::endl;
							if_break = true;
						}
					}
				}
				else
				{
					for (const auto& ch : cur_code)
					{
						if (code_color.find(ch) == code_color.end())
						{
							continue;
						}
						full_code += ch;
					}
					codes.push_back(cur_code);
					if_executing = true;
				}
			}
		}
	}
	return 0;
}

std::int32_t nmspc::Interpreter::scriptBased(std::unordered_map<std::string, std::vector<std::string>>& cmd_value_map)
{
	std::string cur_script_file;
	if (cmd_value_map.find("-f") != cmd_value_map.end() || cmd_value_map.find("--file") != cmd_value_map.end())
	{
		std::string option = (cmd_value_map.find("-f") != cmd_value_map.end() ? "-f" : "--file");
		if (cmd_value_map[option].size() != 1)
		{
			std::cerr << "\033[31m" << "The option \"" << option << "\" only accepts one value" << "\033[0m" << std::endl;
			return 1;
		}
		cur_script_file = cmd_value_map[option][0];
	}
	else
	{
		std::cerr << "\033[31m" << "Script file not specified!" << "\033[0m" << std::endl;
		return 1;
	}
	std::ifstream fin(cur_script_file, std::ios::in);
	std::string full_code;
	if (fin.is_open())
	{
		std::string assist;
		while (std::getline(fin, assist))
		{
			full_code += std::move(assist);
		}
		fin.close();
	}
	else
	{
		std::cerr << "\033[31m" << "File \"" << cur_script_file << "\" does not exist!" << "\033[0m" << std::endl;
		return 1;
	}
	std::uint64_t cur_max_capacity = this->max_capacity;
	if (cmd_value_map.find("-c") != cmd_value_map.end() || cmd_value_map.find("--capacity") != cmd_value_map.end())
	{
		std::string option = (cmd_value_map.find("-c") != cmd_value_map.end() ? "-c" : "--capacity");
		if (cmd_value_map[option].size() != 1)
		{
			std::cerr << "\033[31m" << "The option \"" << option << "\" only accepts one value" << "\033[0m" << std::endl;
			return 1;
		}
		try
		{
			cur_max_capacity = std::stoull(cmd_value_map[option][0]);
			this->paper_tape.resize(cur_max_capacity);
			this->max_capacity = cur_max_capacity;
		}
		catch (const std::exception& e)
		{
			std::cerr << "\033[31m" << "Error: " << e.what() << "\033[0m" << std::endl;
			return 1;
		}
	}
	bool if_visible = (cmd_value_map.find("-v") != cmd_value_map.end() || cmd_value_map.find("--visible") != cmd_value_map.end());
	std::int32_t execute_mode = 0;
	if (cmd_value_map.find("-e") != cmd_value_map.end() || cmd_value_map.find("--execute-mode") != cmd_value_map.end())
	{
		std::string option = (cmd_value_map.find("-e") != cmd_value_map.end() ? "-e" : "--execute-mode");
		if (cmd_value_map[option].size() != 1)
		{
			std::cerr << "\033[31m" << "The option \"" << option << "\" only accepts one value" << "\033[0m" << std::endl;
			return 1;
		}
		std::string execute_mode_str = cmd_value_map[option][0];
		if (execute_mode_str == "step")
		{
			execute_mode = 0;
		}
		else if (execute_mode_str == "fast")
		{
			execute_mode = 1;
		}
		else
		{
			std::cerr << "\033[31m" << "No such execut mode: \"" + execute_mode_str + "\"!" << "\033[0m" << std::endl;
			return 1;
		}
	}
	std::int64_t index = 0;
	std::stack<std::int64_t> left_square_bracket_index;
	std::int64_t code_index = 0;
	bool if_could_execute = true;
	std::int32_t cur_status = 0;
	switch (execute_mode)
	{
	case 0:
		while (static_cast<std::uint64_t>(code_index) < full_code.size())
		{
			cur_status = this->stepExecute(full_code, code_index, if_could_execute, index, left_square_bracket_index);
			if (cur_status != 0) return cur_status;
			if (if_visible)
			{
				this->printPaperTape(index);
			}
		}
		break;
	case 1:
		cur_status = this->fastExecute(full_code, code_index, if_could_execute, index, left_square_bracket_index, if_visible);
		if (cur_status != 0) return cur_status;
		break;
	}
	return 0;
}

std::unordered_map<std::string, std::vector<std::string>> nmspc::Interpreter::parseCommand(const std::vector<std::string>& splited_command)
{
	std::unordered_map<std::string, std::vector<std::string>> cmd_value_map;
	std::string cur_option = "--commandName";
	for (std::int32_t i = 0; i < splited_command.size(); i++)
	{
		if (splited_command[i].size() > 1 && splited_command[i][0] == '-' && !(splited_command[i][1] >= '0' && splited_command[i][1] <= '9'))
		{
			cur_option = splited_command[i];
			if (cmd_value_map.find(cur_option) == cmd_value_map.end())
			{
				cmd_value_map[cur_option] = {};
			}
		}
		else if (!cur_option.empty())
		{
			cmd_value_map[cur_option].push_back(splited_command[i]);
		}
	}
	return cmd_value_map;
}

void nmspc::Interpreter::printPaperTape(const std::int64_t& index)
{
	std::uint64_t begin = std::max(0ll, index - 5);
	std::uint64_t end = std::min(index + 5, (std::int64_t)(this->max_capacity - 1));
	std::cerr << "\033[?25l";
	for (std::uint64_t i = begin; i <= end; i++)
	{
		char outputable_ch = (
			(this->paper_tape[i] >= 0 && this->paper_tape[i] <= 25) ||
			(this->paper_tape[i] >= 27 && this->paper_tape[i] <= 31) ||
			(this->paper_tape[i] == 127) ||
			(this->paper_tape[i] >= -127 && this->paper_tape[i] <= -2)
			) ? ' ' : this->paper_tape[i];
		std::cerr << "\033[32m" << std::setw(5) << std::left << outputable_ch << "\033[0m";
	}
	if (std::cerr.fail() || std::cerr.bad())
	{
		std::cerr.clear();
	}
	std::cerr << '\n';
	for (std::uint64_t i = begin; i <= end; i++)
	{
		std::cerr << "\033[33m" << std::setw(5) << std::left << static_cast<std::int32_t>(this->paper_tape[i]) << "\033[0m";
	}
	if (std::cerr.fail() || std::cerr.bad())
	{
		std::cerr.clear();
	}
	std::cerr << '\n';
	for (std::uint64_t i = begin; i <= end; i++)
	{
		if (i == index) std::cerr << "\033[34m" << std::setw(4) << std::left << "^" << "\033[0m";
		else std::cerr << "\033[34m" << std::setw(5) << std::right << " " << "\033[0m";
	}
	if (std::cerr.fail() || std::cerr.bad())
	{
		std::cerr.clear();
	}
	std::cerr << std::endl;
	std::cerr << "\033[?25h";
}

void nmspc::Interpreter::clearPrintedPaperTape()
{
	std::cerr << "\033[?25l";
	std::cerr << "\033[3A";
	std::cerr << "\033[2K";
	std::cerr << "\033[1B\033[2K";
	std::cerr << "\033[1B\033[2K";
	std::cerr << "\033[2A";
	std::cerr << "\033[?25h";
}

std::int32_t nmspc::Interpreter::entryPoint(int argc, char* argv[])
{
	std::vector<std::string> splited_command(argc);
	for (int i = 0; i < argc; i++)
	{
		splited_command[i] = std::move(std::string(argv[i]));
	}
	std::unordered_map<std::string, std::vector<std::string>> cmd_value_map = std::move(this->parseCommand(splited_command));
	if (cmd_value_map.find("-h") != cmd_value_map.end() || cmd_value_map.find("--help") != cmd_value_map.end())
	{
		std::cerr << "\033[36m" << "Welcome to the BrainFuck interpreter!" << "\033[0m" << std::endl;
		std::cerr << "\033[32m" << "usage; " << "\033[0m" << std::endl;
		std::cerr << "  BrainFuck [[option] [value]]..." << std::endl;
		std::cerr << "\033[32m" << "options: " << "\033[0m" << std::endl;
		std::cerr << "  -h, --help          // Display help document." << std::endl;
		std::cerr << "  -i, --interactive   // Enter interactive mode." << std::endl;
		std::cerr << "  -s, --script        // Enter script based mode." << std::endl;
		std::cerr << "  -f, --file          // Set the script file name." << std::endl;
		std::cerr << "  -c, --capacity      // Set the maximum capacity of the paper tape." << std::endl;
		std::cerr << "  -v, --visible       // Visualize all operations." << std::endl;
		std::cerr << "  -e, --execute-mode  // Spycify the executing mode." << std::endl;
		return 0;
	}
	if (cmd_value_map.find("-i") != cmd_value_map.end() || cmd_value_map.find("--interactive") != cmd_value_map.end())
	{
		return this->interactive(cmd_value_map);
	}
	else if (cmd_value_map.find("-s") != cmd_value_map.end() || cmd_value_map.find("--script") != cmd_value_map.end())
	{
		return this->scriptBased(cmd_value_map);
	}
	return 1;
}
